﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;

namespace Apewer.Web
{

    /// <summary>Response 模型。</summary>
    public abstract class ApiModel : IApiModel
    {

        #region Headers

        int _expires = 0;
        StringPairs _headers = new StringPairs();

        int SafeExpires(int seconds)
        {
            var s = seconds;
            if (s < 0) s = 0;
            if (s > 2592000) s = 2592000;
            return s;
        }

        /// <summary>响应缓存的过期时间，以秒为单位。</summary>
        public virtual int Expires { get => _expires; set => _expires = SafeExpires(value); }

        /// <summary>状态。</summary>
        /// <remarks>默认值：200。</remarks>
        public virtual int Status { get; set; }

        /// <summary>设置 Response 头。</summary>
        public virtual StringPairs Headers { get => _headers; set => _headers = value ?? new StringPairs(); }

        /// <summary>内容类型。当 Headers 中包含 Content-Type 时此属性将被忽略。</summary>
        public virtual string ContentType { get; set; }

        /// <summary>设置文件名，告知客户端此附件处理此响应。</summary>
        public virtual string Attachment { get; set; }

        #endregion

        #region Output

        /// <summary>执行输出。</summary>
        /// <remarks>此方法由 API 调用器发起调用，用户程序不应主动调用。</remarks>
        /// <exception cref="InvalidOperationException"></exception>
        public abstract void Output(ApiContext context);

        /// <summary>向 HTTP 写入头。</summary>
        /// <returns>已写入的头。</returns>
        List<string> WriteHeaders(ApiContext context)
        {
            var status = Status > 0 ? Status : 200;
            if (status != 200) context.Provider.SetStatus(status);

            var headers = Headers;
            var added = new List<string>(32);
            if (headers != null)
            {
                foreach (var header in headers)
                {
                    if (header.Key.IsEmpty()) continue;
                    if (header.Value.IsEmpty()) continue;
                    context.Provider.SetHeader(header.Key, header.Value);
                    added.Add(header.Key.Lower());
                }
            }

            SetAttachment(context);
            context.Provider.SetCache(Expires);
            if (!added.Contains("content-type")) context.Provider.SetContentType(ContentType);
            return added;
        }

        /// <summary>在 Response 头中添加用于设置文件名的属性。</summary>
        void SetAttachment(ApiContext context)
        {
            var name = Attachment;
            if (string.IsNullOrEmpty(name)) return;
            var encoded = TextUtility.EncodeUrl(name);
            context.Provider.SetHeader("Content-Disposition", $"attachment; filename={encoded}");
        }

        /// <summary>输出头和响应体，响应体是字节数组。</summary>
        /// <exception cref="ArgumentNullException" />
        protected void Output(ApiContext context, byte[] bytes)
        {
            if (context == null) throw new ArgumentNullException(nameof(context));

            // 写入头
            if (context.Provider == null) return;
            if (context.Provider.PreWrite().NotEmpty()) return;
            var added = WriteHeaders(context);

            // 写入头
            var length = bytes == null ? 0 : bytes.Length;
            if (!added.Contains("content-length")) context.Provider.SetContentLength(length);

            // 写入主体
            if (length > 0) context.Provider.ResponseBody().Write(bytes);

            // 发送
            context.Provider.Sent();
        }

        /// <summary>以指定参数输出。</summary>
        /// <exception cref="ArgumentNullException" />
        protected void Output(ApiContext context, Stream stream)
        {
            if (context == null) throw new ArgumentNullException(nameof(context));

            // 写入头
            if (context.Provider == null) return;
            if (context.Provider.PreWrite().NotEmpty()) return;
            var added = WriteHeaders(context);

            if (stream == null)
            {
                context.Provider.SetContentLength(0);
                context.Provider.Sent();
            }
            else
            {
                // 写入头
                if (!added.Contains("content-length"))
                {
                    var length = stream.Length - stream.Position;
                    context.Provider.SetContentLength(length);
                }

                // 写入主体
                context.Provider.ResponseBody().Write(stream);

                // 发送
                context.Provider.Sent();
            }
        }

        #endregion

        /// <summary>创建对象实例，并设置默认属性。</summary>
        public ApiModel()
        {
            Status = 200;
            ContentType = "application/octet-stream";
            Expires = 0;
            Attachment = null;
            Headers = new StringPairs();
        }

    }

    /// <summary>输出二进制的 Response 模型。</summary>
    public class ApiBytesModel : ApiModel
    {

        /// <summary>向 Body 写入的字节数组。</summary>
        public byte[] Bytes { get; set; }

        /// <summary>输出字节数组。</summary>
        public override void Output(ApiContext context) => Output(context, Bytes);

        /// <summary>创建对象实例，并设置默认属性。</summary>
        public ApiBytesModel(byte[] bytes = null, string contentType = "application/octet-stream")
        {
            Bytes = bytes;
            ContentType = contentType;
        }

    }

    /// <summary>输出二进制的 Response 模型。</summary>
    public class ApiStreamModel : ApiModel, IDisposable
    {

        /// <summary>将要读取的流，用于向 Body 写入。</summary>
        public Stream Stream { get; set; }

        /// <summary>执行输出后释放流。</summary>
        /// <remarks>默认值：TRUE。</remarks>
        public bool AutoDispose { get; set; }

        /// <summary>输出流。</summary>
        public override void Output(ApiContext context)
        {
            if (AutoDispose)
            {
                using (var stream = Stream) Output(context, Stream);
            }
            else
            {
                Output(context, Stream);
            }
        }

        /// <summary>当指定 AutoDispose 属性时释放流。</summary>
        public void Dispose()
        {
            if (AutoDispose) RuntimeUtility.Dispose(Stream);
        }

        /// <summary>创建对象实例，并设置默认属性。</summary>
        public ApiStreamModel(Stream stream = null, bool autoDispose = true)
        {
            Stream = stream;
            AutoDispose = autoDispose;
        }

    }

    /// <summary>输出二进制的 Response 模型。</summary>
    public class ApiFileModel : ApiModel
    {

        string _path;

        void SetPath(string path)
        {
            if (string.IsNullOrEmpty(path)) throw new FileNotFoundException("没有指定文件路径。");
            if (!File.Exists(path)) throw new FileNotFoundException($"文件 {path} 不存在。");
            _path = path;
        }

        /// <summary>将要读取的文件所在路径，用于向 Body 写入。</summary>
        /// <exception cref="FileNotFoundException"></exception>
        public string Path { get => _path; set => SetPath(value); }

        /// <summary>输出指定路径的文件。</summary>
        public override void Output(ApiContext context)
        {
            if (!File.Exists(Path)) return;

            var info = new FileInfo(Path);
            if (string.IsNullOrEmpty(Attachment)) Attachment = info.Name;

            using (var stream = new FileStream(Path, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                Output(context, stream);
            }
        }

        /// <summary></summary>
        /// <exception cref="FileNotFoundException"></exception>
        public ApiFileModel(string path)
        {
            SetPath(path);
        }

    }

    /// <summary>输出文本的 Response 模型。</summary>
    public class ApiTextModel : ApiModel
    {

        /// <summary>自定义文本。</summary>
        public string Text { get; set; }

        /// <summary>输出文本。</summary>
        public override void Output(ApiContext context) => Output(context, TextUtility.Bytes(Text));

        /// <summary>创建对象实例，并设置默认属性。</summary>
        public ApiTextModel(string text = null, string contentType = "text/plain")
        {
            ContentType = contentType;
            Text = text;
        }

    }

    /// <summary>输出文本的 Response 模型。</summary>
    public class ApiJsonModel : ApiModel
    {

        /// <summary>Json 对象。</summary>
        public Json Json { get; set; }

        /// <summary>缩进排版。</summary>
        /// <remarks>默认值：TRUE</remarks>
        public bool Indented { get; set; }

        /// <summary>转为属性名为驼峰形式。</summary>
        /// <remarks>默认值：FALSE</remarks>
        public bool Camel { get; set; }

        /// <summary>输出文本。</summary>
        public override void Output(ApiContext context)
        {
            var json = (Json != null && Json.Available) ? Json : Json.NewObject();
            if (Camel) Json.Camel(json);
            Output(context, TextUtility.Bytes(json.ToString(Indented)));
        }

        /// <summary>创建对象实例，并设置默认属性。</summary>
        public ApiJsonModel(Json json = null, bool indented = false, bool camel = true)
        {
            ContentType = "application/json";
            Camel = camel;
            Indented = indented;
            Json = json;
        }

    }

    /// <summary>输出重定向的 Response 模型。</summary>
    public class ApiRedirectModel : ApiModel
    {

        /// <summary>将要重定向的位置。</summary>
        public string Location { get; private set; }

        /// <summary>执行重定向。</summary>
        public override void Output(ApiContext context)
        {
            var location = Location;
            if (string.IsNullOrEmpty(location)) return;
            context.Provider.SetRedirect(Location);
        }

        /// <summary>重定向到指定的 URL。</summary>
        /// <exception cref="ArgumentNullException" />
        public ApiRedirectModel(string location)
        {
            if (location.IsEmpty()) throw new ArgumentNullException(nameof(location));
            Location = location;
        }

    }

    /// <summary>输出说明 Exception 的 Response 模型。</summary>
    public class ApiExceptionModel : ApiModel
    {

        /// <summary>要输出的 Exception。</summary>
        public Exception Exception { get; set; }

        /// <summary>解析 Exception 的内容并输出。</summary>
        public override void Output(ApiContext context)
        {
            Status = 500;
            ContentType = "text/plain";
            Output(context, Format(Exception).Bytes());
        }

        /// <summary></summary>
        /// <exception cref="ArgumentNullException" />
        public ApiExceptionModel(Exception exception)
        {
            if (exception == null) throw new ArgumentNullException(nameof(exception));
            Exception = exception;
        }

        /// <summary></summary>
        static string Format(Exception ex)
        {
            var sb = new StringBuilder();
            if (ex == null)
            {
                sb.Append("Invalid Exception");
            }
            else
            {
                try
                {
                    sb.Append(ex.GetType().FullName);

                    var props = ex.GetType().GetProperties();
                    foreach (var prop in props)
                    {
                        var getter = prop.GetGetMethod();
                        if (getter == null) continue;

                        var value = getter.Invoke(ex, null);
                        if (value == null) continue;

                        sb.Append("\r\n\r\n");
                        sb.Append(prop.Name);
                        sb.Append(" : ");
                        sb.Append(prop.PropertyType.FullName);

                        sb.Append("\r\n");
                        if (value is Json) sb.Append(((Json)value).ToString(true));
                        else sb.Append(value.ToString() ?? "");
                    }

                    // sb.Append("\r\n\r\nToString\r\n");
                    // sb.Append("\r\n");
                    // sb.Append(ex.ToString());
                }
                catch { }
            }
            sb.Append("\r\n");
            var text = sb.ToString();
            return text;
        }

    }

    /// <summary>输出带有指定 Status 的 Response 模型。</summary>
    public class ApiStatusModel : ApiModel
    {

        /// <summary>向 Body 写入的字节数组。</summary>
        public byte[] Bytes { get; set; }

        /// <summary>执行重定向。</summary>
        public override void Output(ApiContext context) => Output(context, Bytes);

        /// <summary></summary>
        public ApiStatusModel(int status = 200) => Status = status;

        /// <summary></summary>
        public ApiStatusModel(HttpStatusCode status) => Status = (int)status;

    }

}
